using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.IO;
using Tao.OpenGl;

namespace MKDS_Course_Editor.KCL
{
    public class KclOctreeNode : IDisposable
    {
        public bool _disposed;

        public Collection<KclOctreeNode> Nodes { get; private set; }
        public Collection<ushort> Data { get; private set; }

        public bool Leaf { get { return Nodes.Count == 0; } }

        public int LeafCount
        {
            get
            {
                int count;

                if (Leaf)
                    return 1;

                count = 0;

                for (int i = 0; i < Nodes.Count; i++)
                    count += Nodes[i].LeafCount;

                return count;
            }
        }
        public int BranchCount
        {
            get
            {
                int count;

                if (Leaf)
                    return 0;

                count = 1;

                for (int i = 0; i < Nodes.Count; i++)
                    count += Nodes[i].BranchCount;

                return count;
            }
        }
        public int NodeCount
        {
            get
            {
                int count;

                if (Leaf)
                    return 1;

                count = 1;

                for (int i = 0; i < Nodes.Count; i++)
                    count += Nodes[i].NodeCount;

                return count;
            }
        }
        public int Depth
        {
            get
            {
                int depth;

                if (Leaf)
                    return 1;

                depth = 0;

                for (int i = 0; i < Nodes.Count; i++)
                    depth = Math.Max(depth, Nodes[i].Depth);

                return depth + 1;
            }
        }
        public int LargestLeaf
        {
            get
            {
                int size;

                if (Leaf)
                    return Data.Count;

                size = 0;

                for (int i = 0; i < Nodes.Count; i++)
                    size = Math.Max(size, Nodes[i].LargestLeaf);

                return size;
            }
        }

        public KclOctreeNode(EndianBinaryReader reader, long address)
        {
            long currentAddress, nodeAddress;
            bool leaf;
            int offset;
            ushort data;

            currentAddress = reader.BaseStream.Position;
            offset = reader.ReadInt32();
            leaf = offset < 0;
            offset &= int.MaxValue;

            Nodes = new Collection<KclOctreeNode>();
            Data = new Collection<ushort>();
            reader.BaseStream.Seek(nodeAddress = (address + offset), SeekOrigin.Begin);

            if (leaf)
            {
                reader.BaseStream.Seek(2, SeekOrigin.Current);
                do
                {
                    if (reader.BaseStream.Position == reader.BaseStream.Length)
                        throw new InvalidDataException();
                    data = reader.ReadUInt16();
                    if (data != 0)
                        Data.Add((ushort)(data - 1));
                } while (data != 0);
            }
            else
                for (int i = 0; i < 8; i++)
                    Nodes.Add(new KclOctreeNode(reader, nodeAddress));

            reader.BaseStream.Seek(currentAddress + 4, SeekOrigin.Begin);
        }

        public KclOctreeNode(EndianBinaryReader reader, int nodes)
        {
            long address;

            address = reader.BaseStream.Position;
            Nodes = new Collection<KclOctreeNode>();
            Data = new Collection<ushort>();

            for (int i = 0; i < nodes; i++)
                Nodes.Add(new KclOctreeNode(reader, address));
        }

        public KclOctreeNode()
        {
            Nodes = new Collection<KclOctreeNode>();
            Data = new Collection<ushort>();
        }
        public void DrawNodes(float width, OpenTK.Vector3 start)
        {
            float hw = width / 2.0f;
            float xmin = start.X / 1024f;
            float ymin = start.Y / 1024f;
            float zmin = start.Z / 1024f;
            float xmax = (start.X + hw * 2) / 1024f;
            float ymax = (start.Y + hw * 2) / 1024f;
            float zmax = -(start.Z + hw * 2) / 1024f;
            float[][] box = {new float[]
                {
                    xmin, ymin, zmin
                }, new float[]{
                    xmax, ymin, zmin
                }, new float[]{
                    xmax, ymin, zmin
                }, new float[]{
                    xmax, ymax, zmin
                }, new float[]{
                    xmax, ymax, zmin
                }, new float[]{
                    xmin, ymax, zmin
                }, new float[]{
                    xmin, ymax, zmin
                }, new float[]{
                    xmin, ymax, zmax
                }, new float[]{
                    xmin, ymax, zmax
                }, new float[]{
                    xmin, ymin, zmax
                }, new float[]{
                    xmin, ymin, zmax
                }, new float[]{
                    xmax, ymin, zmax
                }, new float[]{
                    xmax, ymin, zmax
                }, new float[]{
                    xmax, ymax, zmax
                }, new float[]{
                    xmin, ymin, zmin
                }, new float[]{
                    xmin, ymax, zmin
                }, new float[]{
                    xmin, ymin, zmin
                }, new float[]{
                    xmin, ymin, zmax
                }, new float[]{
                    xmax, ymin, zmin
                }, new float[]{
                    xmax, ymin, zmax
                }, new float[]{
                    xmax, ymax, zmin
                }, new float[]{
                    xmax, ymax, zmax
                }, new float[]{
                    xmin, ymax, zmax
                }, new float[]{
                    xmax, ymax, zmax
                }
            };
            Gl.glColor3f(1.0F, 1.0F, 1.0F);
            Gl.glBegin(1);
            for (int i = 0; i < box.Length; i++)
                Gl.glVertex3f(box[i][0], box[i][1], box[i][2]);

            Gl.glEnd();
            if (!Leaf)
            {
                int nr = 0;
                for (int k = 0; k < 2; k++)
                {
                    for (int j = 0; j < 2; j++)
                    {
                        for (int i = 0; i < 2; i++)
                        {
                            Nodes[nr].DrawNodes(hw, start + hw * new OpenTK.Vector3(i, j, k));
                            nr++;
                        }
                    }
                }
            }
        }
        public System.Windows.Forms.TreeNode[] GetNodes()
        {
            List<System.Windows.Forms.TreeNode> t = new List<System.Windows.Forms.TreeNode>();
            for (int i = 0; i < Nodes.Count; i++)
            {
                t.Add(new System.Windows.Forms.TreeNode("Node"));
                t[i].Nodes.AddRange(Nodes[i].GetNodes());
            }
            for (int i = 0; i < Data.Count; i++)
            {
                t.Add(new System.Windows.Forms.TreeNode("Plane (" + Data[i].ToString() + ")"));
            }
            return t.ToArray();
        }
        public void Write(EndianBinaryWriter writer)
        {
            Queue<KclOctreeNode> nodes;
            Queue<long> nodeStarts;
            KclOctreeNode current;
            Collection<ushort> dataSection;
            int progress, totalNodes;
            long freeAddress, startAddress, dataStart;

            startAddress = freeAddress = writer.BaseStream.Position;
            nodes = new Queue<KclOctreeNode>();
            dataSection = new Collection<ushort>();
            nodeStarts = new Queue<long>();

            for (int i = 0; i < Nodes.Count; i++)
            {
                nodeStarts.Enqueue(freeAddress);
                nodes.Enqueue(Nodes[i]);
            }
            freeAddress += Nodes.Count * 4;
            progress = 0;
            totalNodes = Nodes.Count;
            while (nodes.Count > 0)
            {
                current = nodes.Dequeue();

                if (current.Leaf)
                {
                    writer.Write(0);
                    nodeStarts.Dequeue();
                }
                else
                {
                    writer.Write((int)(freeAddress - nodeStarts.Dequeue()));
                    for (int i = 0; i < current.Nodes.Count; i++)
                    {
                        nodes.Enqueue(current.Nodes[i]);
                        nodeStarts.Enqueue(freeAddress);
                        totalNodes++;
                    }
                    freeAddress += 0x20;
                }

                progress++;
                //progressHandler.Invoke(10 + ((45 * progress) / totalNodes), Program.GetString("ProgressWritingOctree"));
            }

            dataStart = writer.BaseStream.Position;
            freeAddress = startAddress;
            writer.BaseStream.Seek(startAddress, SeekOrigin.Begin);
            for (int i = 0; i < Nodes.Count; i++)
            {
                nodeStarts.Enqueue(freeAddress);
                nodes.Enqueue(Nodes[i]);
            }
            freeAddress += Nodes.Count * 4;
            progress = 0;

            while (nodes.Count > 0)
            {
                startAddress = writer.BaseStream.Position;
                current = nodes.Dequeue();

                if (current.Leaf)
                {
                    writer.Write((int)(int.MinValue | (writer.BaseStream.Length - nodeStarts.Dequeue() - 2)));
                    writer.BaseStream.Seek(0, SeekOrigin.End);
                    for (int i = 0; i < current.Data.Count; i++)
                        writer.Write((ushort)(current.Data[i] + 1));
                    writer.Write((ushort)0);

                    for (int i = 0; i < current.Data.Count; i++)
                        dataSection.Add((ushort)(current.Data[i] + 1));
                    dataSection.Add(0);

                    writer.BaseStream.Seek(startAddress + 4, SeekOrigin.Begin);
                }
                else
                {
                    nodeStarts.Dequeue();
                    writer.BaseStream.Seek(4, SeekOrigin.Current);
                    for (int i = 0; i < current.Nodes.Count; i++)
                    {
                        nodes.Enqueue(current.Nodes[i]);
                        nodeStarts.Enqueue(freeAddress);
                    }
                    freeAddress += 0x20;
                }

                progress++;
                //progressHandler.Invoke(55 + ((45 * progress) / totalNodes), Program.GetString("ProgressWritingOctree"));
            }
        }

        public void Dispose()
        {
            if (!_disposed)
            {
                if (!Leaf)
                {
                    for (int i = 0; i < Nodes.Count; i++)
                        Nodes[i].Dispose();
                    Nodes.Clear();
                }
                else
                    Data.Clear();

                Nodes = null;
                Data = null;
                _disposed = true;
            }
        }
    }

}
